/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "hmfs_encoding.h"

#include <unicode/casemap.h>
#include <unicode/uchar.h>
#include <unicode/unistr.h>
#include <unicode/utf8.h>
#include <unordered_map>
#include <sstream>
#include <memory>
#include "hmfs_data.h"

namespace OHOS {
namespace Hmfs {
static const EncodingInfo g_hmfsEncodingMap[] = {
    {"utf8", HMFS_ENC_UTF8_12_1, 0}
};

static const std::unordered_map<std::string, uint16_t> g_encodingFlagsMap = {
    {"strict", 1}
};

const char *Utf8ToWchar(const char *input, wchar_t *wc, size_t insize)
{
    if ((input[0] & 0x80) == 0 && insize >= 1) {
        *wc = (wchar_t) input[0];
        return input + 1;
    }
    if ((input[0] & 0xe0) == 0xc0 && insize >= 2) {
        *wc = (((wchar_t) input[0] & 0x1f) << 6) | ((wchar_t) input[1] & 0x3f);
        return input + 2;
    }
    if ((input[0] & 0xf0) == 0xe0 && insize >= 3) {
        *wc = (((wchar_t) input[0] & 0x0f) << 12) |
            (((wchar_t) input[1] & 0x3f) << 6) |
            ((wchar_t) input[2] & 0x3f);
        return input + 3;
    }
    if ((input[0] & 0xf8) == 0xf0 && insize >= 4) {
        *wc = (((wchar_t) input[0] & 0x07) << 18) |
            (((wchar_t) input[1] & 0x3f) << 12) |
            (((wchar_t) input[2] & 0x3f) << 6) |
            ((wchar_t) input[3] & 0x3f);
        return input + 4;
    }
    if ((input[0] & 0xfc) == 0xf8 && insize >= 5) {
        *wc = (((wchar_t) input[0] & 0x03) << 24) |
            (((wchar_t) input[1] & 0x3f) << 18) |
            (((wchar_t) input[2] & 0x3f) << 12) |
            (((wchar_t) input[3] & 0x3f) << 6) |
            ((wchar_t) input[4] & 0x3f);
        return input + 5;
    }
    if ((input[0] & 0xfe) == 0xfc && insize >= 6) {
        *wc = (((wchar_t) input[0] & 0x01) << 30) |
            (((wchar_t) input[1] & 0x3f) << 24) |
            (((wchar_t) input[2] & 0x3f) << 18) |
            (((wchar_t) input[3] & 0x3f) << 12) |
            (((wchar_t) input[4] & 0x3f) << 6) |
            ((wchar_t) input[5] & 0x3f);
        return input + 6;
    }
    return nullptr;
}

const uint16_t *Utf16ToWchar(const uint16_t *input, wchar_t *wc, size_t insize)
{
    if ((LE16_TO_NATIVE(input[0]) & 0xfc00) == 0xd800) {
        if (insize < 2 || (LE16_TO_NATIVE(input[1]) & 0xfc00) != 0xdc00) {
            return nullptr;
        }
        *wc = ((wchar_t) (LE16_TO_NATIVE(input[0]) & 0x3ff) << 10);
        *wc |= (LE16_TO_NATIVE(input[1]) & 0x3ff);
        *wc += 0x10000;
        return input + 2;
    } else {
        *wc = LE16_TO_NATIVE(*input);
        return input + 1;
    }
}

uint16_t *WcharToUtf16(uint16_t *output, wchar_t wc, size_t outsize)
{
    if (wc <= 0xffff) {
        if (outsize == 0) {
            return nullptr;
        }
        output[0] = NATIVE_TO_LE16(wc);
        return output + 1;
    }
    if (outsize < 2) {
        return nullptr;
    }
    wc -= 0x10000;
    output[0] = NATIVE_TO_LE16(0xd800 | ((wc >> 10) & 0x3ff));
    output[1] = NATIVE_TO_LE16(0xdc00 | (wc & 0x3ff));
    return output + 2;
}

int Utf8ToUtf16(uint16_t *output, const char *input, size_t outsize, size_t insize)
{
    const char *inp = input;
    uint16_t *outp = output;
    wchar_t wc;

    while ((size_t)(inp - input) < insize && *inp) {
        inp = Utf8ToWchar(inp, &wc, insize - (inp - input));
        if (inp == nullptr) {
            HMFS_DEBUG("illegal UTF-8 sequence\n");
            return -EILSEQ;
        }
        outp = WcharToUtf16(outp, wc, outsize - (outp - output));
        if (outp == nullptr) {
            HMFS_DEBUG("name is too long\n");
            return -ENAMETOOLONG;
        }
    }
    *outp = NATIVE_TO_LE16(0);
    return 0;
}

char *WcharToUtf8(char *output, wchar_t wc, size_t outsize)
{
    if (wc <= 0x7f) {
        if (outsize < 1) {
            return nullptr;
        }
        *output++ = (char) wc;
    } else if (wc <= 0x7ff) {
        if (outsize < 2) {
            return nullptr;
        }
        *output++ = 0xc0 | (wc >> 6);
        *output++ = 0x80 | (wc & 0x3f);
    } else if (wc <= 0xffff) {
        if (outsize < 3) {
            return nullptr;
        }
        *output++ = 0xe0 | (wc >> 12);
        *output++ = 0x80 | ((wc >> 6) & 0x3f);
        *output++ = 0x80 | (wc & 0x3f);
    } else if (wc <= 0x1fffff) {
        if (outsize < 4) {
            return nullptr;
        }
        *output++ = 0xf0 | (wc >> 18);
        *output++ = 0x80 | ((wc >> 12) & 0x3f);
        *output++ = 0x80 | ((wc >> 6) & 0x3f);
        *output++ = 0x80 | (wc & 0x3f);
    } else if (wc <= 0x3ffffff) {
        if (outsize < 5) {
            return nullptr;
        }
        *output++ = 0xf8 | (wc >> 24);
        *output++ = 0x80 | ((wc >> 18) & 0x3f);
        *output++ = 0x80 | ((wc >> 12) & 0x3f);
        *output++ = 0x80 | ((wc >> 6) & 0x3f);
        *output++ = 0x80 | (wc & 0x3f);
    } else if (wc <= 0x7fffffff) {
        if (outsize < 6) {
            return nullptr;
        }
        *output++ = 0xfc | (wc >> 30);
        *output++ = 0x80 | ((wc >> 24) & 0x3f);
        *output++ = 0x80 | ((wc >> 18) & 0x3f);
        *output++ = 0x80 | ((wc >> 12) & 0x3f);
        *output++ = 0x80 | ((wc >> 6) & 0x3f);
        *output++ = 0x80 | (wc & 0x3f);
    } else {
        return nullptr;
    }

    return output;
}

int Utf16Toutf8(char *output, const uint16_t *input, size_t outsize, size_t insize)
{
    const uint16_t *inp = input;
    char *outp = output;
    wchar_t wc;

    while ((size_t)(inp - input) < insize && LE16_TO_NATIVE(*inp)) {
        inp = Utf16ToWchar(inp, &wc, insize - (inp - input));
        if (inp == nullptr) {
            HMFS_DEBUG("illegal UTF-16 sequence\n");
            return -EILSEQ;
        }
        outp = WcharToUtf8(outp, wc, outsize - (outp - output));
        if (outp == nullptr) {
            HMFS_DEBUG("name is too long\n");
            return -ENAMETOOLONG;
        }
    }
    *outp = '\0';
    return 0;
}

int32_t EncodingStrToValue(std::string& encodingString, uint16_t& value)
{
    for (uint32_t i = 0 ; i < HMFS_ARRAY_SIZE(g_hmfsEncodingMap); i++) {
        if (encodingString == g_hmfsEncodingMap[i].encodingName) {
            value = g_hmfsEncodingMap[i].encodingKey;
            return 0;
        }
    }
    return -1;
}

int32_t EncodingValueToStr(const uint16_t encodingVal, std::string& str)
{
    for (uint32_t i = 0 ; i < HMFS_ARRAY_SIZE(g_hmfsEncodingMap); i++) {
        if (g_hmfsEncodingMap[i].encodingKey == encodingVal) {
            str = g_hmfsEncodingMap[i].encodingName;
            return 0;
        }
    }
    return -1;
}

uint16_t GetDefaultEncodingFlag(uint16_t encoding)
{
    for (uint32_t i = 0 ; i < HMFS_ARRAY_SIZE(g_hmfsEncodingMap); i++) {
        if (g_hmfsEncodingMap[i].encodingKey == encoding) {
            return g_hmfsEncodingMap[i].initialFlags;
        }
    }
    return 0;
}

int32_t EncodingFlagStrToValue(std::string& flagsStr, uint16_t& flags)
{
    std::stringstream flagList(flagsStr);
    std::string flagStr;
    while (std::getline(flagList, flagStr, ',')) {
        bool negFlag = false;
        if (flagStr.find("no") == 0) {
            negFlag = true;
            flagStr.erase(0, strlen("no"));
        }

        auto it = g_encodingFlagsMap.find(flagStr);
        if (it == g_encodingFlagsMap.end()) {
            return -1;
        }

        if (negFlag) {
            HMFS_INFO("Sub %s", flagStr.c_str());
            flags &= ~it->second;
        } else {
            HMFS_INFO("Add %s", flagStr.c_str());
            flags |= it->second;
        }
    }
    return 0;
}

bool IsNeedEncoding(int32_t encoding)
{
    if (encoding == HMFS_ENC_UTF8_12_1) {
        return true;
    }
    return false;
}

int32_t CaseFold(const unsigned char *str, size_t len, unsigned char *dest, size_t dlen)
{
    if (str == nullptr || dest == nullptr || len == 0 || dlen == 0) {
        HMFS_ERROR("Invalid input parameters");
        return -EINVAL;
    }

    // Allocate a buffer for the case-folded result
    UErrorCode errorCode = U_ZERO_ERROR;
    icu::UnicodeString input(reinterpret_cast<const char*>(str), len, "UTF-8");
    std::vector<char16_t> buffer(input.length());
    int32_t resultLength = icu::CaseMap::fold(U_FOLD_CASE_DEFAULT, input.getBuffer(), input.length(),
        buffer.data(), buffer.size(), nullptr, errorCode);
    if (U_FAILURE(errorCode)) {
        HMFS_ERROR("Error case folding string: %s", u_errorName(errorCode));
        return -EINVAL;
    }

    // Convert the result to UTF-8
    icu::UnicodeString caseFolded;
    caseFolded.setTo(false, buffer.data(), resultLength);
    std::string utf8Result;
    caseFolded.toUTF8String(utf8Result);
    if (utf8Result.size() > dlen) {
        HMFS_ERROR("Result exceeds destination buffer size");
        return -ENAMETOOLONG;
    }

    std::copy(utf8Result.begin(), utf8Result.end(), dest);
    return utf8Result.size();
}


} // namespace Hmfs
} // namespace OHOS